import { isSameVnode } from "./index";

export function patch(oldVnode, vnode) {
    if(!oldVnode){
        //这就是组件的挂载
        return createElm(vnode);//vm.$el 对应的就是组件渲染的结果
    }

    //oldVnode可能是后续做虚拟节点的时候 是两个虚拟节点的比较
    // console.log(oldVnode,vnode);
    const isRealElement = oldVnode.nodeType;//如果有说明他是一个dom元素
    if (isRealElement) {
        const oldElm = oldVnode;

        //需要获取父节点 将当前节点的下一个元素作为参照物将他插入 之后删除老节点
        const parentNode = oldElm.parentNode;//父节点
        let el = createElm(vnode); //根据虚拟节点替换
        parentNode.insertBefore(el, oldElm.nextSibling);//老节点的下一个元素的前面
        parentNode.removeChild(oldElm);
        return el;
    } else {
        //diff算法
        //1.两个节点不是同一个节点 直接删除老的 换上新的(没有比对了)
        //2.两个节点是同一个节点(判断节点的tag和节点的key) 比较两个节点的属性是否有差异(复用老的节点，将差异的属性更新)
        //3.节点比较完毕后需要比较两人的儿子

        return patchVnode(oldVnode, vnode);
    }
}

function patchVnode(oldVnode, vnode) {
    if (!isSameVnode(oldVnode, vnode)) {//tag ==tag key == key
        //用老节点的父亲 进行替换
        let el = createElm(vnode)
        oldVnode.el.parentNode.replaceChild(el, oldVnode.el);
        return el;
    }
    //纯文本情况 我们期望比较一下文本的内容 oldVnode.tag==undefined
    let el = vnode.el = oldVnode.el; //复用老节点的元素
    if (!oldVnode.tag) {
        //是文本
        if (oldVnode.text !== vnode.text) {
            el.textContent = vnode.text;//用新的文本覆盖掉老的
        }
    }

    //是标签 需要比对标签的属性
    patchProps(el, oldVnode.data, vnode.data);

    //比较儿子节点
    //1.一方有儿子 一方没有儿子
    //2.两方都有儿子
    let oldChildren = oldVnode.children || [];
    let newChildren = vnode.children || [];

    if (oldChildren.length > 0 && newChildren.length > 0) {
        //完整的diff算法 需要比较两个人的儿子
        updateChildren(el, oldChildren, newChildren);
    } else if (newChildren.length > 0) {
        //没有老的 有新的
        mountChildren(el, newChildren);
    } else if (oldChildren.length > 0) {
        //新的没有 老的有 要删除
        el.innerHTML = '';
    }

    // console.log(oldChildren,newChildren);
    return el;
}

function mountChildren(el, newChildren) {
    for (let i = 0; i < newChildren.length; i++) {
        let child = newChildren[i];
        el.appendChild(createElm(child));
    }
}
function updateChildren(el, oldChildren, newChildren) {
    //vue2中采用双指针的方式比较两个节点
    let oldStartIndex = 0;
    let newStartIndex = 0;
    let oldEndIndex = oldChildren.length - 1;
    let newEndIndex = newChildren.length - 1;

    let oldStartVnode = oldChildren[0];
    let newStartVnode = newChildren[0];

    let oldEndVnode = oldChildren[oldEndIndex];
    let newEndVnode = newChildren[newEndIndex];

    function makeIndexByKey(children) {
        let map = {

        }
        children.forEach((child, index) => {
            map[child.key] = index;
        })
        return map;
    }
    let map = makeIndexByKey(oldChildren);
    // console.log(map); {a: 0, b: 1, c: 2, d: 3}

    //面试题：循环的时候为什么+key
    //在给动态列表添加key的时候 要尽量避免用索引 因为索引前后都是从0开始 可能会发生错误复用
    while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
        //双方有一方头部指针大于尾部指针则停止循环
        if (!oldStartVnode) {
            oldStartVnode = oldChildren[++oldStartIndex];
        } else if (!oldEndVnode) {
            oldEndVnode = oldChildren[--oldEndIndex]
        }
        //比较外层 tag key
        //头头比对
        else if (isSameVnode(oldStartVnode, newStartVnode)) {
            //比较内层 如果是相同节点 则递归比较子节点
            patchVnode(oldStartVnode, newStartVnode);
            oldStartVnode = oldChildren[++oldStartIndex];
            newStartVnode = newChildren[++newStartIndex];
            //比较开头节点 往后移
        } else if (isSameVnode(oldEndVnode, newEndVnode)) {//尾尾比对
            //比较内层 如果是相同节点 则递归比较子节点
            patchVnode(oldEndVnode, newEndVnode);
            oldEndVnode = oldChildren[--oldEndIndex];
            newEndVnode = newChildren[--newEndIndex];
            //比较开头节点 往后移
        } else if (isSameVnode(oldEndVnode, newStartVnode)) {//交叉比对 abcd -> dabc
            patchVnode(oldEndVnode, newStartVnode);
            //先移动 再赋值
            el.insertBefore(oldEndVnode.el, oldStartVnode.el);//将老的尾巴移动到老的前面去
            oldEndVnode = oldChildren[--oldEndIndex];
            newStartVnode = newChildren[++newStartIndex];
        } else if (isSameVnode(oldStartVnode, newEndVnode)) {//交叉比对 abcd -> bcda
            patchVnode(oldStartVnode, newEndVnode);
            //insertBefore具备移动性 会将原来的元素移动走
            // el.appendChild(oldStartVnode.el);
            el.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling);//oldEndVnode.el.nextSibling->null
            oldStartVnode = oldChildren[++oldStartIndex];
            newEndVnode = newChildren[--newEndIndex];
        } else {
            //乱序比对
            //根据老的列表做一个映射关系 用新的去找，找到则移动，找不到则添加，最后多余的就删除
            let moveIndex = map[newStartVnode.key];//如果拿到则说明是我要移动的索引
            if (moveIndex !== undefined) {
                let moveVnode = oldChildren[moveIndex];//找到对应的虚拟节点 复用
                el.insertBefore(moveVnode.el, oldStartVnode.el);
                oldChildren[moveIndex] = undefined;//表示这个节点已经移动走了
                patchVnode(moveVnode, newStartVnode);
            } else {
                el.insertBefore
                    (createElm(newStartVnode), oldStartVnode.el);
            }
            newStartVnode = newChildren[++newStartIndex];
        }
    }


    if (newStartIndex <= newEndIndex) {//新的多了 插入新的
        for (let i = newStartIndex; i <= newEndIndex; i++) {
            let childEl = createElm(newChildren[i]);
            //这里可能是向后追加 也有可能是向前追加
            // el.appendChild(childEl);
            let anchor = newChildren[newEndIndex + 1] ? newChildren[newEndIndex + 1].el : null;//获取下一个元素

            el.insertBefore(childEl, anchor);//anchor为null的时候则会认为是appendChild
        }
    }

    if (oldStartIndex <= oldEndIndex) {//老的多了 删除老的
        for (let i = oldStartIndex; i <= oldEndIndex; i++) {
            if (oldChildren[i]) {
                let childEl = oldChildren[i].el;
                el.removeChild(childEl);
            }
        }
    }
}

export function patchProps(el, oldProps = {}, props = {}) {
    //老的属性中有 新的没有 要删除老的
    let oldStyles = {} || oldProps.style;
    let newStyles = {} || props.style;
    for (let key in oldStyles) { //新的没有 老的 样式 删除
        if (!newStyles[key]) {
            el.style[key] = '';
        }
    }

    for (let key in oldProps) {//新的没有 老的 属性
        if (!props[key]) {
            el.removeAttribute(key)
        }
    }

    for (let key in props) { //用新的覆盖掉老的
        if (key === 'style') {
            for (let styleName in props.style) {
                el.style[styleName] = props.style[styleName];
            }
        } else {
            el.setAttribute(key, props[key]);
        }
    }
}

function createComponent(vnode){
    let i = vnode.data;
    if((i = i.hook) && (i = i.init)){
        i(vnode);//调用vdom里面的data.hook里面的init方法
    }
    if(vnode.componentInstance){
        return true;//说明是组件
    }
}

export function createElm(vnode) {
    let { tag, data, children, text } = vnode;
    if (typeof tag === 'string') {//标签
        //创建真实元素 也要区分是组件还是元素
        if(createComponent(vnode)){ //组件 vnode.componentInstance.$el
            return vnode.componentInstance.$el;
        }

        vnode.el = document.createElement(tag);//后续我们需要diff算法 拿虚拟节点比对后更新dom
        patchProps(vnode.el, {}, data);
        children.forEach(child => {
            vnode.el.appendChild(createElm(child));//递归渲染
        })
    } else {//文本
        vnode.el = document.createTextNode(text);
    }
    return vnode.el;//从根虚拟节点创建真实节点
}

//每次更新页面的话 dom结构式不会变的
//我调用render方法是，数据变化了会根据数据渲染成新的虚拟节点，用新的虚拟节点渲染dom